iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
Software Development

異步程式設計修煉:Kotlin Coroutines 完全指南系列 第 24 篇

Day 23:Flow 底層其實是 Channel,兩者的關係是什麼

  • 分享至 

  • xImage
  •  

Day 22:Channel,協程之間怎麼傳話 停在一個問題上:Channel 讓一個協程把資料一筆一筆接力遞交給另一個協程,而收集 Flow 的時候,資料同樣是依序一筆一筆交到收集端手中,這兩件事聽起來很像,Flow 底層會不會其實也用到了類似 Channel 的機制?今天要正面回答這個問題。

兩件看起來很像的事

Day 22 建立的心智模型裡,Channel 靠著發送端與接收端,把資料一筆一筆接力遞交出去。Day 19 建立的 Flow 心智模型裡,收集一個 Flow,資料同樣是依序一筆一筆被送進收集端手中。兩者的行為描述放在一起看,確實有種似曾相識的感覺。

答案是肯定的,而且不只是行為上恰好相似。某些建立 Flow 的方式,底層確實是透過 Channel 完成值的暫停發送與依序接收,Flow 與 Channel 之間存在具體的實作關聯,不是兩套各自獨立、只是碰巧長得像的機制。

用 Channel 的角度重新看一次 Flow 在做什麼

把某些建立 Flow 的方式攤開來看,本質上是啟動了一個負責產生值的協程,這個協程把每個值透過類似 Day 22 定案的 Channel 發送出去。收集 Flow 的那一端,則像是從這個 Channel 依序接收資料,一筆一筆處理。當產生值的速度與收集值的速度不一致時,兩端能夠自然銜接,靠的正是 Channel 具備的暫停等待特性,發送端在需要時暫停,接收端在需要時等待,開發者完全不需要自己動手處理這件事。

拿多來源圖片下載與聚合工具具體化這件事。回到 Day 19 建立的「持續回報下載進度百分比」這個 Flow,假設進度更新的速度,比畫面處理進度的速度還要快,底層依賴的正是類似 Channel 的暫停機制,讓產生進度數值的一方,在收集端還來不及處理上一個數值時,暫停等待一下,而不是讓數值無限堆積在某個地方,也不會讓還沒被收集的數值就這樣憑空消失。這正是 Day 19 提到的冷流、可取消這些特性,能夠穩定運作的底層依據之一。

這也解釋了一件容易被忽略的事:Flow 不需要開發者手動管理發送端與接收端,卻依然具備依序處理、暫停等待這些特性,原因在於這些特性有一部分正是繼承自 Channel 本身具備的能力。Flow 是在 Channel 這類機制之上,包裝出一套更貼近「描述一段資料處理流程」的宣告式介面,讓使用的人只需要專心描述想對這串值做哪些處理,不需要親自操作發送與接收的細節。這裡可以借用 Day 22 提過的傳送帶比喻簡短理解,Flow 像是把傳送帶這整套機制包裝起來,使用的人不需要自己操作傳送帶的啟動與停止,只需要描述想在傳送帶上做哪些加工。

不是所有 Flow 都直接對應一個 Channel

看到這裡,很容易冒出一個過度推論:既然某些 Flow 底層用了 Channel,是不是所有 Flow 都是 Channel 包裝出來的另一種寫法?這個推論需要在這裡明確擋下來。

並非每一種建立 Flow 的方式,底層都必然直接對應一個 Channel。某些單純的 Flow 建立方式,本質上更接近依序執行一段程式碼,把每個值依序往下傳遞給收集端,並不一定需要真的啟動另一個協程、透過 Channel 傳遞資料。Day 19 示範的那個最基礎的 flow 建構器搭配 emit 的寫法,執行邏輯與收集邏輯其實可以在同一個協程的執行脈絡裡依序完成,不必然涉及另外開一個協程與一個 Channel。

今天要建立的認識,是「Flow 與 Channel 具備密切的實作關聯,且在某些情境下確實透過 Channel 運作」,而不是「所有 Flow 都是 Channel 的另一種寫法」這個過度簡化的結論。每一種建立方式底層各自的實作細節,超出今天需要處理的範圍,這裡只需要建立方向性的認識,避免把兩者直接畫上等號。

什麼時候該用 Flow,什麼時候該直接用 Channel

底層關係釐清之後,回到今天標題真正想回答的問題:兩者各自適合什麼場合。

Flow 適合用在描述一段資料如何被產生、處理、最終被收集的情境。開發者可以用宣告式的方式組合操作符,把一連串轉換攤開成一條清楚的資料處理流程,不需要自己管理協程的啟動,也不需要自己處理發送與接收的細節。多來源圖片下載與聚合工具裡持續回報的下載進度,就是很自然的例子,重點是那一串隨時間展開的數值本身,以及要對這串數值做哪些處理。

Channel 適合用在協程之間需要明確、直接地點對點傳遞資料,且傳遞雙方各自的角色分工很清楚的情境。Day 22 示範的一個協程專心負責發送、另一個協程專心負責接收,就是這種明確分工的例子。這種時候,直接使用 Channel 會比包裝成 Flow 更直觀,因為要解決的問題本來就是「甲協程要把東西交給乙協程」,而不是「描述一串值該怎麼被處理」。

兩者關係密切,卻不是彼此的替代品。選哪一個,取決於當下真正要解決的問題是什麼:想描述的是一段資料處理流程,答案通常是 Flow;想建立的是協程之間一條明確的傳話管道,答案通常是 Channel。

兩者關係密切,但不是彼此的替代品

今天把 Day 19 定案的 Flow 錨點,與 Day 22 定案的 Channel 錨點,正式接了起來。某些建立 Flow 的方式,底層確實透過 Channel 完成值的暫停發送與依序接收,讓生產資料與消費資料的速度不一致時,能靠著暫停等待自然銜接,不需要開發者手動介入。同時也明確保留了限定說明,並非所有建立 Flow 的方式都必然對應一個 Channel,兩者是關係密切、但定位不同的工具,Flow 負責描述資料處理流程,Channel 負責協程之間點對點的明確傳話。

不過,今天談到的暫停等待,處理的都是速度差距還在合理範圍內的情境。如果生產資料的速度天生就比消費資料的速度快上許多,例如多個來源同時瘋狂回報下載進度,畫面處理更新的速度卻遠遠跟不上,光靠暫停等待這一招,真的能應付所有這類情境嗎?

下一篇會正式定案這個問題的答案,詳見 《Day 24:背壓是什麼,生產太快時該怎麼踩煞車》。


上一篇
Day 22:Channel,協程之間怎麼傳話
下一篇
Day 24:背壓是什麼,生產太快時該怎麼踩煞車
系列文
異步程式設計修煉:Kotlin Coroutines 完全指南 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言